// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This test is POSIX only.

#include "ipc/ipc_message_attachment_set.h"

#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>

#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "ipc/ipc_platform_file_attachment_posix.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace IPC {
namespace {

    // Get a safe file descriptor for test purposes.
    int GetSafeFd()
    {
        return open("/dev/null", O_RDONLY);
    }

    // Returns true if fd was already closed.  Closes fd if not closed.
    bool VerifyClosed(int fd)
    {
        const int duped = dup(fd);
        if (duped != -1) {
            EXPECT_NE(IGNORE_EINTR(close(duped)), -1);
            EXPECT_NE(IGNORE_EINTR(close(fd)), -1);
            return false;
        }
        return true;
    }

    // The MessageAttachmentSet will try and close some of the descriptor numbers
    // which we given it. This is the base descriptor value. It's great enough such
    // that no real descriptor will accidently be closed.
    static const int kFDBase = 50000;

    TEST(MessageAttachmentSet, BasicAdd)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        ASSERT_EQ(set->size(), 0u);
        ASSERT_TRUE(set->empty());
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
        ASSERT_EQ(set->size(), 1u);
        ASSERT_TRUE(!set->empty());

        // Empties the set and stops a warning about deleting a set with unconsumed
        // descriptors
        set->CommitAllDescriptors();
    }

    TEST(MessageAttachmentSet, BasicAddAndClose)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        ASSERT_EQ(set->size(), 0u);
        ASSERT_TRUE(set->empty());
        const int fd = GetSafeFd();
        ASSERT_TRUE(set->AddAttachment(
            new internal::PlatformFileAttachment(base::ScopedFD(fd))));
        ASSERT_EQ(set->size(), 1u);
        ASSERT_TRUE(!set->empty());

        set->CommitAllDescriptors();

        ASSERT_TRUE(VerifyClosed(fd));
    }
    TEST(MessageAttachmentSet, MaxSize)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        for (size_t i = 0; i < MessageAttachmentSet::kMaxDescriptorsPerMessage; ++i)
            ASSERT_TRUE(set->AddAttachment(
                new internal::PlatformFileAttachment(kFDBase + 1 + i)));

        ASSERT_TRUE(
            !set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));

        set->CommitAllDescriptors();
    }

#if defined(OS_ANDROID)
#define MAYBE_SetDescriptors DISABLED_SetDescriptors
#else
#define MAYBE_SetDescriptors SetDescriptors
#endif
    TEST(MessageAttachmentSet, MAYBE_SetDescriptors)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        ASSERT_TRUE(set->empty());
        set->AddDescriptorsToOwn(NULL, 0);
        ASSERT_TRUE(set->empty());

        const int fd = GetSafeFd();
        static const int fds[] = { fd };
        set->AddDescriptorsToOwn(fds, 1);
        ASSERT_TRUE(!set->empty());
        ASSERT_EQ(set->size(), 1u);

        set->CommitAllDescriptors();

        ASSERT_TRUE(VerifyClosed(fd));
    }

    TEST(MessageAttachmentSet, PeekDescriptors)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        set->PeekDescriptors(NULL);
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));

        int fds[1];
        fds[0] = 0;
        set->PeekDescriptors(fds);
        ASSERT_EQ(fds[0], kFDBase);
        set->CommitAllDescriptors();
        ASSERT_TRUE(set->empty());
    }

    TEST(MessageAttachmentSet, WalkInOrder)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        // TODO(morrita): This test is wrong. TakeDescriptorAt() shouldn't be
        // used to retrieve borrowed descriptors. That never happens in production.
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 1)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 2)));

        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(0)->TakePlatformFile(), kFDBase);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(1)->TakePlatformFile(),
            kFDBase + 1);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(2)->TakePlatformFile(),
            kFDBase + 2);

        set->CommitAllDescriptors();
    }

    TEST(MessageAttachmentSet, WalkWrongOrder)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        // TODO(morrita): This test is wrong. TakeDescriptorAt() shouldn't be
        // used to retrieve borrowed descriptors. That never happens in production.
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 1)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 2)));

        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(0)->TakePlatformFile(), kFDBase);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(2), nullptr);

        set->CommitAllDescriptors();
    }

    TEST(MessageAttachmentSet, WalkCycle)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        // TODO(morrita): This test is wrong. TakeDescriptorAt() shouldn't be
        // used to retrieve borrowed descriptors. That never happens in production.
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 1)));
        ASSERT_TRUE(
            set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 2)));

        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(0)->TakePlatformFile(), kFDBase);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(1)->TakePlatformFile(),
            kFDBase + 1);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(2)->TakePlatformFile(),
            kFDBase + 2);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(0)->TakePlatformFile(), kFDBase);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(1)->TakePlatformFile(),
            kFDBase + 1);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(2)->TakePlatformFile(),
            kFDBase + 2);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(0)->TakePlatformFile(), kFDBase);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(1)->TakePlatformFile(),
            kFDBase + 1);
        ASSERT_EQ(set->GetNonBrokerableAttachmentAt(2)->TakePlatformFile(),
            kFDBase + 2);

        set->CommitAllDescriptors();
    }

#if defined(OS_ANDROID)
#define MAYBE_DontClose DISABLED_DontClose
#else
#define MAYBE_DontClose DontClose
#endif
    TEST(MessageAttachmentSet, MAYBE_DontClose)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        const int fd = GetSafeFd();
        ASSERT_TRUE(set->AddAttachment(new internal::PlatformFileAttachment(fd)));
        set->CommitAllDescriptors();

        ASSERT_FALSE(VerifyClosed(fd));
    }

    TEST(MessageAttachmentSet, DoClose)
    {
        scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);

        const int fd = GetSafeFd();
        ASSERT_TRUE(set->AddAttachment(
            new internal::PlatformFileAttachment(base::ScopedFD(fd))));
        set->CommitAllDescriptors();

        ASSERT_TRUE(VerifyClosed(fd));
    }

} // namespace
} // namespace IPC
